package mop.domain.task;

import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;


import mop.domain.resource.Resource;
import mop.domain.resource.ResourcePool;

import mop.domain.user.*;
import mop.exception.IllegalDeadlineException;
import mop.exception.IllegalDependencyException;
import mop.exception.IllegalDuplicationException;
import mop.exception.IllegalDescriptionException;
import mop.exception.IllegalStatusException;
import mop.exception.IllegalTaskException;
import mop.exception.IllegalTerminationException;

/**
 * Class for TaskPool.
 * This class is a singleton!
 * 
 * @invar	No two tasks have the same name.
 * 			|foreach(description) 
 *  		| isUniqueTask(description)
 *  
 * @author	MOP Team 7
 */
public class TaskPool {

	/**
	 * Database HashMap containing user, tasks pairs
	 */
	private Map<String,List<Task>> tasks;
	
	/**
	 * Create a TaskPool with no extra settings
	 * Constructor must be at protected level
	 * allows inheretance but no out of package creation
	 * 
	 * @post tasks is set to new hashmap
	 *  |this.tasks instanceof HashMap
	 */
	protected TaskPool()
	{
		tasks = new HashMap<String,List<Task>>();
	}
	
	/***************************************************************************
	 * 							instance
	 ***************************************************************************/
	
	/**
	 * Use this function to acces TaskPool always
	 * 
	 * @return the static instance of TaskPool
	 */
	public static TaskPool getInstance()
	{
		if(instance == null)
		{
			instance = new TaskPool();
		}
		return instance;
	}
	
	/**
	 * instance to self
	 */
	private static TaskPool instance = null;
	
	/***************************************************************************
	 * 							Tasks
	 ***************************************************************************/
	 
	/**
	  * Checks if the passed description is not already a task
	  * in the container
	  * 
	  * @param description	The description you want checked
	  * 
	  * @return true If description cannot be found in the tasks map
	  */
	 public boolean isUniqueTask(String description)
	 {
		if(findTask(description) != null)
			return false;
		return true;
	 }
	 
	 /**
	  * Get a certain task by description
	  * 	for use by other pools
	  * 
	  * @param description	Description of the task you want to get
	  *  
	  * @return The task if found
	  * 
	  * @throws IllegalTaskException
	  *  If the task could not be found
	  */
	 public Task getTask(String description) throws IllegalTaskException
	 {
		Task t;
		if((t = findTask(description)) != null)
			 return t;
		
		 throw new IllegalTaskException(description);
	 }
	 
	/**
	 * Find task by given description, over all users
	 * 
	 * @param description	Description of task
	 *  
	 * @return
	 *  Task if found
	 *  null otherwise
	 */
	private Task findTask(String description)
	{
		for(List<Task> l : tasks.values())
		{
			for(Task t : l)
			{
				if(t.getDescription().equals(description))
				{
					return t;
				}
			}
		}
		return null;
	}
		
	/**
	 * Find task by given description from current user
	 * 
	 * @param description	Description of task
	 * 
	 * @return
	 *  Task if exists
	 * 	null otherwise
	 *  	|findTaskByUser(UserPool.getInstance().getCurrentUser(),description);
	 */
	private Task findTaskByUser(String description)
	{
		return findTaskByUser(UserPool.getInstance().getCurrentUser(), description);
	}
	
	/**
	 * Find task by given user and task description
	 * 
	 descriptionaram user	Owner of the task
	 * @param description	Description of the task
	 *  
	 * @return
	 * 	Task if exists
	 * 	null otherwise
	 */
	private Task findTaskByUser(String user,String description)
	{
		for(Task t : tasks.get(user))
		{
			if(t.getDescription() == description)
			{
				return t;
			}
		}
		return null;
	}

	/***************************************************************************
	 * 							Create task
	 ***************************************************************************/
	 
	/**
	 * Creates new task
	 * 
	 * @param description		Description of task
	 * @param start		Start date of task
	 * @param deadline	Deadline of task
	 * @param duration	Expected duration of task
	 * @param res		Task resources
	 * @param dep		Task dependencies
	 *  
	 * @effect Task instance created
	 *  |new Task(description, start,deadline,duration,res,dependencies)
	 *  
	 * @effect Task added to pool
	 * 	|addTask(UserPool.getInstance().getCurrentUser(),task)
	 *  
	 * @throws IllegalDeadlineException
	 * @throws IllegalDescriptionException
	 * @throws IllegalDuplicationException
	 * 	If a task with similar description already exists in the database
	 */
	public void createTask(String description, Timestamp start, Timestamp deadline, Timestamp duration, List<String> res, List<String> dep) 
	throws IllegalDeadlineException, IllegalDescriptionException, IllegalDuplicationException
	{

		if(!isUniqueTask(description))
		{
			throw new IllegalDuplicationException(description);
		}
		
		List<Task> dependencies = stringListToTaskList(dep);
		
		List<Resource> resources = ResourcePool.getInstance().getResourceList(res);
		
		Task task = new Task(description, start,deadline,duration,resources,dependencies);
		addTask(UserPool.getInstance().getCurrentUser(),task);
	}
	
	/**
	 * Add task to pool
	 * 
	 * @param user	Owner of task
	 * @param task	Task to be added
	 * 
	 * @post task is in pool
	 *  |tasks.get(user).contains(task) == true
	 */
	private void addTask(String user,Task task)
	{
		if(this.tasks.get(user) == null)
			this.tasks.put(user, new LinkedList<Task>());
		
		this.tasks.get(user).add(task);
	}
	
	/***************************************************************************
	 * 							Remove task
	 ***************************************************************************/
	/**
	 * Try to remove task
	 * 
	 * @param task	Task to be removed
	 * 
	 * @throws IllegalTerminationException 
	 * 
	 * @effect If task has no parents, task will be removed from pool
	 *  |removeTask(task)
	 *  
	 * @effect If task has no parents, task will be terminated
	 *  |task.terminate(true);
	 *  
	 * @return	List with description of parents of task if task has parents, null otherwise
	 */
	private List<String> tryRemoveTask(Task task) 
	{
		//task has parents, so return list with parents
		if(task.getAllParents().size() > 0)
			return taskListToStringList(task.getAllParents());
		
		//task has no parents, remove task
		removeTask(task);
		task.terminate();
		
		return null;
	}
	
	/**
	 * Try to remove task
	 * 
	 * @param task	Task to be removed
	 * 
	 * @throws IllegalTerminationException 
	 * 
	 * @effect If task has no parents, task will be removed
	 *  |removeTask(task)
	 *  
	 * @return	List with parent descriptions of task if task has parents, null otherwise
	 */
	public List<String> tryRemoveTask(String description) 
	{
		return tryRemoveTask(findTask(description));
	}
	
	/**
	 * Remove task and all parents if required
	 * 
	 * @param task		Task to be removed
	 * @param parents	Whether to terminate and remove task parents
	 * 
	 * @throws IllegalTerminationException 
	 * 
	 * @effect If required, remove parents from pool
	 *  |for(Task t : task.getAllParents())
	 *	|	removeTask(t)
	 * 
	 * @effect Remove task from pool
	 *  |removeTask(task)
	 *
	 * @effect Terminate task and parents if required
	 *  |task.terminate(parents)
	 */
	private void forceRemoveTask(Task task, boolean parents) 
	throws IllegalTerminationException
	{
		//Remove task parents from pool
		if(parents)
		{
			for(Task t : task.getAllParents())
			{
				removeTask(t);
			}
		}
		
		removeTask(task);
		task.terminate(parents);
		
	}
	
	/**
	 * Remove task and all parents if required for given task description
	 * 
	 * @param task		Description of task to be removed
	 * @param parents	Whether to terminate and remove task parents
	 * 
	 * @throws IllegalTerminationException 
	 * 
	 * @effect Remove task according to settings
	 *  |forceRemoveTask(findTask(task), parents)
	 */
	public void forceRemoveTask(String task, boolean parents) 
	throws IllegalTerminationException
	{
		forceRemoveTask(findTask(task), parents);
	}
	
	/**
	 * Remove task from pool
	 * 
	 * @param task	Task to be removed
	 * 
	 * @throws IllegalTerminationException
	 * 
	 * @post Task is removed pool
	 *  |findTask(task.getDescription()) == null
	 */
	private void removeTask(Task task)
	{
		for(List<Task> l : tasks.values())
		{
			if(l.contains(task))
			{
				l.remove(task);
			}
		}
	}

	/***************************************************************************
	 * 							Get task details
	 ***************************************************************************/

	
	/**
	 * Return the status of the task with given description
	 * Doesn't differentiate between available and unavailable
	 * 
	 * @param description	Description of the task
	 * 
	 * @return status
	 */
	public String getStatus(String description)
	{
		Task task = findTask(description);
		return task.getStatus();
	}
	
	/**
	 * Returns the extended status
	 * The extended status differentiates between available and unavailable when the task is not yet finished
	 *
	 * @param description	Description of the task
	 * 
	 * @return
	 *  Status available if task is available
	 *  Status unavailable if task is unavailable because of an unfinished dependency or an unavailable resource
	 *  Normal status if task is finished
	 *  	|getStatus()
	 */
	public String getExtendedStatus(String description)
	{
		Task task = findTask(description);
		return task.getExtendedStatus();
	}
	
	/**
	 * Returns list with start date, duration and deadline of task
	 * 
	 * @param description	Description of task
	 * 
	 * @return	List with timestamps of date, duration and deadline
	 */
	public List<Timestamp> getSchedule(String description)
	{
		Task task = findTask(description);
		return task.getSchedule();
	}
	
	/**s
	 * Returns list with descriptions of dependent tasks for given task description
	 * 
	 * @param description	Name of task to return dependencies for
	 * 
	 * @return	List of descriptions of tasks that given task depends on
	 */
	public List<String> getDependencies(String description)
	{
		Task task = findTask(description);
		List<Task> dep = task.getDependencies();
		return taskListToStringList(dep);
	}
	
	/**
	 * Returns list with descriptions of resources for given task description
	 * 
	 * @param description	Description of task to return resources
	 * 
	 * @return	List of descriptions of resources that given task uses
	 */
	public List<String> getResources(String description)
	{
		Task task = findTask(description);
		List<String> list = new LinkedList<String>();
		for(Resource r : task.getResources())
		{
			list.add(r.toString());
		}
		return list;
	}
	
	/***************************************************************************
	 * 							Update task details
	 ***************************************************************************/
	/**
	 * Update task status
	 * 
	 * @param description		Description of the task
	 * @param status	New task status
	 * @throws IllegalStatusException 
	 */
	public void updateStatus(String description,String status) throws IllegalStatusException
	{
		Task task = findTask(description);
		task.setStatus(status);
	}
	
	/**
	 * Update task details
	 * 
	 * @param olddescription	Old description of task
	 * @param description		New description of task
	 * @param start		Start date of task
	 * @param deadline	Deadline date of task
	 * @param duration	Duration date of task
	 * @param res		List of resources task uses
	 * @param dep		List of tasks task depends on
	 * 
	 * @effect details get updated
	 *  	|t.updateDetails(description,start,deadline,duration,resources,dependencies)
	 * 
	 * @throws IllegalDuplicateException
	 * 	if the task is a duplicate
	 * 		|!TaskPool.isUniqueresource(description)
	 * 
	 * @throws IllegalDeadlineException
	 *  Invalid deadline
	 *  
	 * @throws IllegalDependencyException
	 *  Loop in dependency graph
	 *  	|!Task.isValidDependency(dep)
	 *  
	 * @throws IllegalDescriptionException
	 *  Illegal description
	 *  	|!Task.isValidDescription(description)
	 */
	public void updateDetails(String olddescription, String description, Timestamp start, 
			Timestamp deadline, Timestamp duration, List<String> res, List<String> dep) 
	throws IllegalDuplicationException, IllegalDeadlineException, IllegalDependencyException, IllegalDescriptionException 
	{
		if(!olddescription.equals(description))
		{
			if(!isUniqueTask(description))
			{
				throw new IllegalDuplicationException(description);
			}
		}
		
		Task t = findTaskByUser(olddescription);
		
		List<Task> dependencies = stringListToTaskList(dep);
		
		List<Resource> resources = ResourcePool.getInstance().getResourceList(res);
		
		t.updateDetails(description,start,deadline,duration,resources,dependencies);
	}
	
	/**
	 * List with strings go in, List with tasks goes out
	 * 
	 * @param list	List with strings
	 * @return	List with corresponding tasks
	 */
	private List<Task> stringListToTaskList(List<String> list)
	{
		List<Task> out = new LinkedList<Task>();
		if(list != null)
		{
			for(String s : list)
			{
				Task t = findTask(s);
				if(t != null)
					out.add(t);
			}
		}
		return out;
	}
	

	/**
	 * Creates a list with descriptions of tasks on given list of tasks
	 * 
	 * @param in	List of tasks
	 * 
	 * @return	List of strings with descriptions of tasks
	 */
	private List<String> taskListToStringList(List<Task> in)
	{
		LinkedList<List<Task>> list = new LinkedList<List<Task>>();
		list.add(in);
		return taskListToStringList(list);
	}
	

	/**
	 * Creates a list with strings of objects on given list of collection
	 * 
	 * @param in	List of tasks
	 * 
	 * @return	List of strings with descriptions of tasks
	 */
	private List<String> taskListToStringList(Collection<List<Task>> in)
	{
		List<String> out = new LinkedList<String>();
		if(in != null)
		{
			Iterator<List<Task>> iterator = in.iterator();
			
			while(iterator.hasNext())
			{
				List<Task> temp = iterator.next();
				for(int i = 0; i < temp.size(); i++)
				{
					out.add(temp.get(i).getDescription());
				}
			}
		}
		return out;
	}
	
	/**
	 * Returns list with descriptions of all tasks in pool
	 * 
	 * @return List of strings representing descriptions of all tasks in pool
	 *  |taskListToStringList(tasks.values());
	 */
	public List<String> getAllTasksList()
	{
		return taskListToStringList(tasks.values());
	}
	
	/**
	 * Returns list with descriptions of all tasks in pool of current user
	 * 
	 * @return List with descriptions of tasks of current user
	 */
	public List<String> getUserTasksList()
	{
		List<Task> list = tasks.get(UserPool.getInstance().getCurrentUser());
		return taskListToStringList(list);
	}
	
	/**
	 * Returns list with focus tasks
	 * 
	 * @return 
	 *  List with tasks of current user that end today, sorted by deadline
	 *  If no tasks must be finished today, it show the top 10 tasks with earliest deadlines
	 */
	@SuppressWarnings("deprecation")
	public List<String> getFocusTasksList()
	{
		List<Task> list = new LinkedList<Task>();
		try {
			list.addAll(tasks.get(UserPool.getInstance().getCurrentUser()));
		} catch (NullPointerException e) {
			this.tasks.put(UserPool.getInstance().getCurrentUser(), new LinkedList<Task>());
		}
		
		if(list != null)
		{
			//make a copy, so we don't iterate & modify over the same list (would create a concurrent modification exception)
			List<Task> copy = new LinkedList<Task>();
			copy.addAll(list);
			
			//we only want unfinished tasks, no need to focus on already finished tasks
			for(Task t : copy)
			{
				if(t.getStatus() != "unfinished")
					list.remove(t);
			}
			
			//sort the unfinished tasks
			TaskPool.mergeSort(list);
			
			int start = -1, stop = 0;
			
			//create timestamps for today at 00:00, and tomorrow at 00:00
			Calendar cal = Calendar.getInstance();
			Timestamp today = new Timestamp(cal.getTimeInMillis());
			today.setHours(0);
			today.setMinutes(0);
			today.setSeconds(0);
			
			cal.add(Calendar.DATE, +1);
			Timestamp tomorrow = new Timestamp(cal.getTimeInMillis());
			tomorrow.setHours(0);
			tomorrow.setMinutes(0);
			tomorrow.setSeconds(0);
	
			//Select start & stop
			for(int i = 0; i < list.size(); i++)
			{
				if(start == -1 && list.get(i).getDeadline().after(today))
				{
					start = i;
				}
				if(start > -1 && list.get(i).getDeadline().before(tomorrow))
				{
					stop = i;
				}
			}
			
			//if start is below zero, there are no tasks for or after today
			if(start < 0)
				return null;
			
			//if stop is zero 
			if(stop == 0)
				stop = Math.min(list.size()-1,start+9);
			
			return taskListToStringList(list.subList(start, stop+1));
		}
		return null;
	}

    /**
     * Mergesort algorithm.
     * @param a an array of Comparable items.
     */
    public static void mergeSort( List<Task> a ) {
        Task [ ] tmpArray = new Task[ a.size() ];
        mergeSort( a, tmpArray, 0, a.size() - 1 );
    }
    
    /**
     * Internal method that makes recursive calls.
     * @param a an array of Comparable items.
     * @param tmpArray an array to place the merged result.
     * @param left the left-most index of the subarray.
     * @param right the right-most index of the subarray.
     */
    private static void mergeSort( List<Task> a, Task [ ] tmpArray,
            int left, int right ) {
        if( left < right ) {
            int center = ( left + right ) / 2;
            mergeSort( a, tmpArray, left, center );
            mergeSort( a, tmpArray, center + 1, right );
            merge( a, tmpArray, left, center + 1, right );
        }
    }
    
    /**
     * Internal method that merges two sorted halves of a subarray.
     * @param a an array of Comparable items.
     * @param tmpArray an array to place the merged result.
     * @param leftPos the left-most index of the subarray.
     * @param rightPos the index of the start of the second half.
     * @param rightEnd the right-most index of the subarray.
     */
    private static void merge( List<Task> a, Task [ ] tmpArray,
            int leftPos, int rightPos, int rightEnd ) {
        int leftEnd = rightPos - 1;
        int tmpPos = leftPos;
        int numElements = rightEnd - leftPos + 1;
        
        // Main loop
        while( leftPos <= leftEnd && rightPos <= rightEnd )
            if( a.get(leftPos).compareTo( a.get(rightPos) ) <= 0 )
                tmpArray[ tmpPos++ ] = a.get(leftPos++);
            else
                tmpArray[ tmpPos++ ] = a.get(rightPos++);
        
        while( leftPos <= leftEnd )    // Copy rest of first half
            tmpArray[ tmpPos++ ] = a.get(leftPos++);
        
        while( rightPos <= rightEnd )  // Copy rest of right half
            tmpArray[ tmpPos++ ] = a.get(rightPos++);
        
        // Copy tmpArray back
        for( int i = 0; i < numElements; i++, rightEnd-- )
        	a.set(rightEnd,tmpArray[ rightEnd ]);
 
    }
	

}
